package dev.ece.suit.loader.models.obj;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import dev.ece.core.cells.model.action.ActionManager.Action;
import dev.ece.suit.loader.Face;
import dev.ece.suit.loader.FrameModelDepthShader;
import dev.ece.suit.loader.FrameModelShader;
import dev.ece.suit.loader.Joint;
import dev.ece.suit.loader.Material;
import dev.ece.suit.loader.Mesh;
import dev.ece.suit.loader.ModelDepthShader;
import dev.ece.suit.loader.ModelLoader;
import dev.ece.suit.loader.ModelShader;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;

public class ObjModelLoader implements ModelLoader<Integer> {

	private List<Vector3f> vertice = new ArrayList<Vector3f>();
	
	private List<Vector2f> uv = new ArrayList<Vector2f>();
	
	private List<Vector3f> normals = new ArrayList<Vector3f>();
	
	private List<ObjFace> faces = new ArrayList<ObjFace>();
	
	private List<ObjMaterial> materials = new ArrayList<ObjMaterial>();
	
	private List<ObjMeshValue> meshvalues = new ArrayList<ObjMeshValue>();
	
	private Map<String, Integer> materialIndexMap = new ConcurrentHashMap<String, Integer>();
	
	public BufferedReader mtlReader;
	
	public BufferedReader reader;
	
	private Vector3f[] boundBox = new Vector3f[2];
	
	public ObjModelLoader(InputStream input) {
		this.reader = new BufferedReader(new InputStreamReader(input));
	}
	
	public ObjModelLoader(InputStream mltInput,  InputStream input) {
		this.reader = new BufferedReader(new InputStreamReader(input));
		if(mltInput != null) {
			this.mtlReader = new BufferedReader(new InputStreamReader(mltInput));
		}
	}
	
	@Override
	public void load() throws IOException {
		String line = null;
		String[] data = null;
		//材质信息
		if(this.mtlReader != null) {
			ObjMaterial material = null;
			while((line = mtlReader.readLine()) != null) {
				data = StringUtils.split(StringUtils.trimToEmpty(line), " ");
				if(data.length > 0) {
					if(StringUtils.equals("newmtl", data[0])) {
						material = new ObjMaterial();
						material.setDefaultMaterial();
						materials.add(material);
						materialIndexMap.put(data[1], materials.size() - 1);
					} else if(StringUtils.equals("Ns", data[0])) {
						
					} else if(StringUtils.equals("Kd", data[0])) {
						material.getDiffuse().set(
								NumberUtils.toFloat(data[1]), 
								NumberUtils.toFloat(data[2]), 
								NumberUtils.toFloat(data[3]));
					} else if(StringUtils.equals("Ks", data[0])) {
						material.getSpecular().set(
								NumberUtils.toFloat(data[1]), 
								NumberUtils.toFloat(data[2]), 
								NumberUtils.toFloat(data[3]));
					} else if(StringUtils.equals("map_Kd", data[0])) {
						material.setTextureName(data[1]);
					}
				}
			}
		}
		
		//物体信息
		ObjMeshValue mesh = new ObjMeshValue();
		Vector3f vertex = null;
		Vector3f point = null;
		while((line = reader.readLine()) != null) {
			data = StringUtils.split(StringUtils.trimToEmpty(line), " ");
			if(data.length > 0) {
				if(StringUtils.equals("o", data[0])) {
					continue;
				} else if(StringUtils.equals("v", data[0])) {
					vertex = new Vector3f(
							NumberUtils.toFloat(data[1]),
							NumberUtils.toFloat(data[2]),
							NumberUtils.toFloat(data[3]));
					vertice.add(vertex);
					if(boundBox[0] == null) {
						boundBox[0] = new Vector3f(vertex);
						boundBox[1] = new Vector3f(vertex);
					} else {
						point = boundBox[0];
						point.set(
								Math.min(vertex.getX(), point.getX()), 
								Math.min(vertex.getY(), point.getY()), 
								Math.min(vertex.getZ(), point.getZ()));
						point = boundBox[1];
						point.set(
								Math.max(vertex.getX(), point.getX()), 
								Math.max(vertex.getY(), point.getY()), 
								Math.max(vertex.getZ(), point.getZ()));
					}
				} else if(StringUtils.equals("vt", data[0])) {
					uv.add(new Vector2f(
							NumberUtils.toFloat(data[1]),
							NumberUtils.toFloat(data[2])));
				} else if(StringUtils.equals("vn", data[0])) {
					normals.add(new Vector3f(
							NumberUtils.toFloat(data[1]),
							NumberUtils.toFloat(data[2]),
							NumberUtils.toFloat(data[3])));
				} else if(StringUtils.equals("usemtl", data[0])) {
					if(meshvalues.isEmpty()) {
						if(mesh.getEnd() > 0) {
							meshvalues.add(mesh);
						}
					}
					int start = mesh != null ? mesh.getEnd(): 0;
					
					//使用材质
					mesh = new ObjMeshValue();
					mesh.setStart(start);
					mesh.setEnd(start);
					if(materials.size() > 0) {
						if(materialIndexMap.containsKey(data[1])) {
							mesh.setMaterialIndex(materialIndexMap.get(data[1]));
						}
					}
					meshvalues.add(mesh);
				} else if(StringUtils.equals("f", data[0])) {
					faces.add(dev.ece.suit.loader.models.obj.ObjFace.createFace(data));
					mesh.setEnd(mesh.getEnd() + 1);
				}
			}
		}
		if(meshvalues.isEmpty()) {
			meshvalues.add(mesh);
		}
	}
	
	@Override
	public Integer total() {
		return 0;
	}

	@Override
	public int frameCount() {
		return 0;
	}

	@Override
	public Material[] getMaterials() {
		return materials.toArray(new ObjMaterial[] {});
	}

	@Override
	public Mesh[] getMeshes() {
		ObjMesh[] meshes = new ObjMesh[this.meshvalues.size()];
		ObjMeshValue objmesh = null;
		for(int i=0; i<meshes.length; i++) {
			objmesh = this.meshvalues.get(i);
			meshes[i] = new ObjMesh(objmesh.toIndice(), objmesh.getMaterialIndex());
			meshes[i].fillData(faces, this.vertice, this.uv, this.normals);
		}
		return meshes;
	}

	@Override
	public Face[] getFaces() {
		return faces.toArray(new Face[] {});
	}

	@Override
	public Joint<Integer>[] getUpdateJoints() {
		return null;
	}

	@Override
	public ModelShader getModelShader() {
		return FrameModelShader.getDefaultModelShader();
	}

	@Override
	public ModelDepthShader getModelDepthShader() {
		return FrameModelDepthShader.getDefaultModelDepthShader();
	}

	@Override
	public Vector3f[] boundBox() {
		return this.boundBox;
	}

	@Override
	public void addAction(Map<String, Action<Integer>> actions) {}

	@Override
	public void dispose() {
		if(mtlReader != null) {
			try {
				mtlReader.close();
				mtlReader = null;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(reader != null) {
			try {
				this.reader.close();
				reader = null;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(vertice != null) {
			vertice.clear();
			vertice = null;
		}
		if(uv != null) {
			uv.clear();
			uv = null;
		}
		if(normals != null) {
			normals.clear();
			normals = null;
		}
		if(faces != null) {
			faces.clear();
			faces = null;
		}
		if(faces != null) {
			materials.clear();
			materials = null;
		}
		if(faces != null) {
			meshvalues.clear();
			meshvalues = null;
		}
		if(faces != null) {
			materialIndexMap.clear();
			materialIndexMap = null;
		}
	}

}
